﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using Moonlite.Compiling;
using Moonlite.Data.EditorInterop;
using Moonlite.Data.Mpq;
using Moonlite.ExtensionApi;
using Moonlite.ExtensionApi.ControlInterfaces;
using Moonlite.ExtensionApi.GlobalInterfaces;
using Moonlite.Platform.General;
using Moonlite.Services.CodeCompletion;

namespace Moonlite.UI.Shell.Controls
{
    public partial class MapExplorer : UserControl, IMapExplorer
    {
        private static readonly Color FlagForeColor = Color.Black;
        private static readonly Color PropertyForeColor = Color.Black;
        private TreeNode CreateNodeWithForeColor(string text, Color foreColor)
        {
            TreeNode node = new MapExplorerNode(text);
            node.ForeColor = foreColor;
            return node;
        }

        private void SafeAddTreeNodeAddAction(TreeNode parent, TreeNode nodeToAdd)
        {
            parent.Nodes.Add(nodeToAdd);
        }

        private void SafeAddTreeNode(TreeNode parent, TreeNode nodeToAdd)
        {
            if ((parent.TreeView != null && parent.TreeView.InvokeRequired) || (treeView != null && treeView.InvokeRequired))
            {
                parent.TreeView.Invoke((Action<TreeNode, TreeNode>)SafeAddTreeNodeAddAction, new object[] { parent, nodeToAdd });
            }
            else
            {
                parent.Nodes.Add(nodeToAdd);
            }
        }

        private void SafeNodeClearAction(TreeNode node)
        {
            node.Nodes.Clear();
        }

        private void SafeNodeClear(TreeNode node)
        {
            if (node.TreeView.InvokeRequired)
            {
                node.TreeView.Invoke((Action<TreeNode>)SafeNodeClearAction, new object[] { node });
            }
            else
            {
                node.Nodes.Clear();
            }
        }

        private void AddProperties(object @object, TreeNode node)
        {
            Type type = @object.GetType();
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.Name != "Children" && property != GetDefaultProperty(type) && property != GetDisplayProperty(type))
                {
                    string value = property.PropertyType.IsArray ? ArrayUtils.ArrayToStringGeneric((IList)property.GetValue(@object, null), ",") : property.GetValue(@object, null).ToString();
                    TreeNode propertyNode = CreateNodeWithForeColor(property.Name + ": " + value, PropertyForeColor);
                    propertyNode.ImageIndex = AndromedaImageIndexes.ImageIndexProperty;
                    propertyNode.SelectedImageIndex = propertyNode.ImageIndex;
                    SafeAddTreeNode(node, propertyNode);
                }
            }
        }

        private PropertyInfo GetDefaultProperty(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(DefaultPropertyAttribute), false);
            if (attributes.Length > 0)
            {
                DefaultPropertyAttribute actualAttribute = (DefaultPropertyAttribute)attributes[0];
                return type.GetProperty(actualAttribute.Name);
            }

            return null;
        }

        private PropertyInfo GetDisplayProperty(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(DisplayNameAttribute), false);
            if (attributes.Length > 0)
            {
                DisplayNameAttribute actualAttribute = (DisplayNameAttribute)attributes[0];
                return type.GetProperty(actualAttribute.DisplayName);
            }

            return null;
        }

        private void AddChildrenAsNodes(IDeserializedObject @object, TreeNode currentNode)
        {
            Type type = @object.GetType();
            foreach (IDeserializedObject childObject in @object.Children)
            {
                Type childObjectType = childObject.GetType();
                PropertyInfo defaultProperty = GetDefaultProperty(childObjectType);
                PropertyInfo displayProperty = GetDisplayProperty(childObjectType);
                TreeNode objectNode = new MapExplorerNode(childObjectType.Name + (defaultProperty != null ? " [" + defaultProperty.Name + ": " + defaultProperty.GetValue(childObject, null) + (displayProperty != null ? ", " + displayProperty.Name + ": " + displayProperty.GetValue(childObject, null) : "") + "]" : ""));
                objectNode.ImageIndex = AndromedaImageIndexes.ImageIndexClass;
                objectNode.SelectedImageIndex = objectNode.ImageIndex;
                SafeAddTreeNode(currentNode, objectNode);
                AddProperties(childObject, objectNode);
                if (childObject.Children.Count > 0)
                {
                    TreeNode objectChildrenNode = new MapExplorerNode("Children");
                    objectChildrenNode.ImageIndex = AndromedaImageIndexes.ImageIndexNamespace;
                    objectChildrenNode.SelectedImageIndex = objectChildrenNode.ImageIndex;
                    objectChildrenNode.Expand();
                    SafeAddTreeNode(objectNode, objectChildrenNode);
                    AddChildrenAsNodes(childObject, objectChildrenNode);
                }
            }
        }

        private void ParseRegionsCore(XmlNode node, TreeNodeCollection nodes)
        {
            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (!(childNode is XmlDeclaration))
                {
                    string attributesText = "";
                    int index = 0;
                    foreach (XmlAttribute attribute in childNode.Attributes)
                    {
                        if (index == 0)
                        {
                            attributesText = "[";
                        }

                        attributesText += attribute.Name + ": " + attribute.Value;
                        if (index != childNode.Attributes.Count - 1)
                        {
                            attributesText += ", ";
                        }
                        else
                        {
                            attributesText += "]";
                        }

                        index++;
                    }

                    TreeNode childNodeTreeNode = new TreeNode(childNode.Name + " " + attributesText, AndromedaImageIndexes.ImageIndexClass, AndromedaImageIndexes.ImageIndexClass);
                    nodes.Add(childNodeTreeNode);
                    if (childNode.ChildNodes.Count > 0)
                    {
                        TreeNode childrenTreeNode = new TreeNode("Children [Count: " + childNode.ChildNodes.Count.ToString() + "]", AndromedaImageIndexes.ImageIndexNamespace, AndromedaImageIndexes.ImageIndexNamespace);
                        childrenTreeNode.Tag = "Children";
                        childNodeTreeNode.Nodes.Add(childrenTreeNode);
                        ParseRegionsCore(childNode, childrenTreeNode.Nodes);
                        childrenTreeNode.Expand();
                    }
                }
            }
        }

        private void ParseRegions(string xml)
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(xml);
            ParseRegionsCore(document, treeView.Nodes);
        }

        private void ParseMapFiles(string mapFilePath)
        {
            try
            {
                treeView.Nodes.Clear();
                if (File.Exists(mapFilePath))
                {
                    using (TMPQArchive archive = new TMPQArchive(mapFilePath))
                    {
                        if (archive.Exists("Objects"))
                        {
                            string tempFileName = Path.GetTempFileName();
                            archive.ExportFile("Objects", tempFileName);
                            ObjectsDeserializer deserializer = new ObjectsDeserializer();
                            DeserializedObjectCollection objects = null;
                            using (StreamReader reader = new StreamReader(tempFileName))
                            {
                                objects = deserializer.BuildTree(reader.ReadToEnd());
                            }

                            foreach (IDeserializedObject @object in objects)
                            {
                                TreeNode objectNode = new MapExplorerNode(@object.GetType().Name);
                                objectNode.ImageIndex = AndromedaImageIndexes.ImageIndexNamespace;
                                objectNode.SelectedImageIndex = objectNode.ImageIndex;
                                objectNode.Expand();
                                treeView.Nodes.Add(objectNode);
                                AddChildrenAsNodes(@object, objectNode);
                            }

                            treeView.Sort();
                            File.Delete(tempFileName);
                        }
                    }

                    using (TMPQArchive archive = new TMPQArchive(mapFilePath))
                    {
                        if (archive.Exists("Regions"))
                        {
                            string tempFileName = Path.GetTempFileName();
                            archive.ExportFile("Regions", tempFileName);
                            using (StreamReader reader = new StreamReader(tempFileName))
                            {
                                ParseRegions(reader.ReadToEnd());
                            }

                            File.Delete(tempFileName);
                        }
                    }
                }
            }
            catch (IOException exception)
            {
                Console.Write(exception.Message);
            }
        }

        public void RefreshObjects()
        {
            if (Global.Instance.Project != null)
            {
                string mapFilePath = Global.Instance.Project.GetMapPath();
                if (File.Exists(mapFilePath))
                {
                    ParseMapFiles(mapFilePath);
                }
            }
        }

        private void OnRefreshItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            RefreshObjects();
        }

        private void OnTreeViewNodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node is MapExplorerNode && ((MapExplorerNode)e.Node).DoubleClickAction != null)
            {
                ((MapExplorerNode)e.Node).DoubleClickAction((MapExplorerNode)e.Node);
            }
        }

        internal void Loaded()
        {
            Global.Instance.ProjectOpened += (object sender, ProjectEventArgs e) =>
            {
                RefreshObjects();
            };

            Global.Instance.ProjectClosed -= (object sender, ProjectEventArgs e) =>
            {
                treeView.Nodes.Clear();
            };
        }

        private int AddImageListToImageList(ImageList imageList, ImageList source)
        {
            int count = 0;
            foreach (Image image in source.Images)
            {
                imageList.Images.Add(image);
                count++;
            }

            return count;
        }

        public MapExplorer()
        {
            InitializeComponent();
            try { Global.Instance.RegisterService(this, GetType().GetInterface("IMapExplorer")); }
            catch { }
            treeView.ImageList = AndromedaImageIndexes.GlobalImageList;
            treeView.TreeViewNodeSorter = new MapExplorerNodeSorter();
        }
    }
}
